package ru.hei.pointsserver.service

import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import ru.hei.pointsserver.entities.House
import ru.hei.pointsserver.entities.PointsLog
import ru.hei.pointsserver.entities.Professor
import ru.hei.pointsserver.repository.HousesRepository
import ru.hei.pointsserver.repository.PointsLogRepository
import ru.hei.pointsserver.repository.ProfessorRepository
import ru.hei.pointsserver.rest.entities.request.AddPoints
import ru.hei.pointsserver.rest.entities.request.AddProfessor
import ru.hei.pointsserver.rest.entities.response.HouseResponse
import java.util.*

@Service
@Transactional(readOnly = true)
class PointsService @Autowired constructor(
  val professorRepository: ProfessorRepository,
  val housesRepository: HousesRepository,
  val logRepository: PointsLogRepository
) {
  private final val logger = LoggerFactory.getLogger(PointsService::class.java)

  fun getPoints(house: String): Mono<HouseResponse> {
    return housesRepository.findByNameIgnoreCase(house.lowercase())
      .map { map(it) }
  }

  fun getAllPoints(): Flux<HouseResponse> {
    return housesRepository.findAll()
      .map { map(it) }
  }

  @Transactional
  fun addPoints(house: String, pointsRequest: AddPoints): Mono<HouseResponse> {
    return professorRepository.existsById(pointsRequest.professorId)
      .doOnError { logger.error("Professor ${pointsRequest.professorId} not found") }
      .filter { true == it }
      .flatMap { housesRepository.findByNameIgnoreCase(house) }
      .doOnNext { logger.info("Got ${it.points} for $house") }
      .flatMap { housesRepository.save(it.apply { points += pointsRequest.points }) }
      .doOnNext { logger.info("Saved ${it.points} for $house") }
      .flatMap {
        logRepository.save(
          PointsLog(
            professorId = pointsRequest.professorId,
            houseId = it.id!!,
            points = pointsRequest.points
          )
        )
      }
      .doOnNext { logger.info("Saved log $it") }
      .map { HouseResponse(house, it.points) }
  }

  @Transactional
  fun addProfessor(professor: AddProfessor): Mono<UUID> {
    return professorRepository.save(map(professor))
      .doOnNext { logger.info("Professor ${professor.name} created") }
      .onErrorResume { professorRepository.findByNameIgnoreCase(professor.name) }
      .mapNotNull { it.id }
  }

  companion object Mapper {
    private fun map(professor: AddProfessor) =
      Professor(name = professor.name)

    private fun map(house: House) =
      HouseResponse(house.name, house.points)
  }
}
